﻿namespace Cellfish.Toolbox.Collections.Concurrent
{
    using System;
    using System.Collections;
    using System.Collections.Concurrent;
    using System.Collections.Generic;
    using System.Threading;

    using Cellfish.Toolbox.Core;

    /// <summary>
    /// Implements a thread safe list with properties from a Linked List;
    /// Inserting/removing head and tail is O(1). Removing any other element is O(n).
    /// </summary>
    /// <typeparam name="T">Specifies the element type of the linked list.</typeparam>
    public class ConcurrentLinkedList<T> : IProducerConsumerCollection<T>
    {
        private readonly LinkedList<T> list;

        private SpinLock listLock = new SpinLock(true);

        /// <summary>
        /// Initializes a new instance of the <see cref="ConcurrentLinkedList{T}"/> class.
        /// List will be empty.
        /// </summary>
        public ConcurrentLinkedList()
            : this(null)
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="ConcurrentLinkedList{T}"/> class.
        /// </summary>
        /// <param name="collection">The IEnumerable whos items are copied into the ConcurrentLinkedList&lt;T&gt;.</param>
        public ConcurrentLinkedList(IEnumerable<T> collection)
        {
            if (collection == null)
            {
                this.list = new LinkedList<T>();
            }
            else
            {
                this.list = new LinkedList<T>(collection);    
            }
        }

        /// <summary>
        /// Returns a <see cref="System.String" /> that represents this instance.
        /// </summary>
        /// <returns>
        /// A <see cref="System.String" /> that represents this instance.
        /// </returns>
        public override string ToString()
        {
            return string.Format("{0} items with lock {1}locked", this.list.Count, this.listLock.IsHeld ? string.Empty : "un");
        }

        /// <summary>
        /// Determines whether the specified <see cref="System.Object"/>, is equal to this instance.
        /// </summary>
        /// <param name="obj">The <see cref="System.Object" /> to compare with this instance.</param>
        /// <returns>
        ///   <c>true</c> if the specified <see cref="System.Object" /> is equal to this instance; otherwise, <c>false</c>.
        /// </returns>
        public override bool Equals(object obj)
        {
            return EqualityUtility.Equals<ConcurrentLinkedList<T>>(this, obj, ConcurrentLinkedList<T>.Equals);
        }

        private static bool Equals(ConcurrentLinkedList<T> a, ConcurrentLinkedList<T> b)
        {
            return a.list.GetHashCode() == b.list.GetHashCode();
        }

        /// <summary>
        /// Returns a hash code for this instance.
        /// </summary>
        /// <returns>
        /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table. 
        /// </returns>
        public override int GetHashCode()
        {
            return EqualityUtility.GetHashCode(this.ExecuteWithLock<int>(this.GetHashCodeInternal), this.listLock.GetHashCode());
        }

        private int GetHashCodeInternal()
        {
            return this.list.GetHashCode();
        }

        #region LinkedList methods

        /// <summary>
        /// Gets the first item in the list.
        /// </summary>
        /// <value>
        /// The first item in the list. If the list is empty then default value for the item type is returned.
        /// </value>
        public T First
        {
            get
            {
                return this.ExecuteWithLock<T>(this.FirstInternal);
            }
        }

        private T FirstInternal()
        {
            if (this.list.First == null)
            {
                return default(T);
            }

            return this.list.First.Value;
        }

        /// <summary>
        /// Gets the last item in the list.
        /// </summary>
        /// <value>
        /// The last item in the list. If the list is empty then default value for the item type is returned.
        /// </value>
        public T Last
        {
            get
            {
                return this.ExecuteWithLock<T>(this.LastInternal);
            }
        }

        private T LastInternal()
        {
            if (this.list.Last == null)
            {
                return default(T);
            }

            return this.list.Last.Value;
        }

        /// <summary>
        /// Adds a new node containing the specified value at the start of the list.
        /// </summary>
        /// <param name="item">The item.</param>
        public void AddFirst(T item)
        {
            this.ExecuteWithLock(this.AddFirstInternal, item);
        }

        private void AddFirstInternal(T item)
        {
            this.list.AddFirst(item);
        }

        /// <summary>
        /// Adds a new node containing the specified value at the end of the list.
        /// </summary>
        /// <param name="item">The item.</param>
        public void AddLast(T item)
        {
            this.ExecuteWithLock(this.AddLastInternal, item);
        }

        private void AddLastInternal(T item)
        {
            this.list.AddLast(item);
        }

        /// <summary>
        /// Removes all items from the list.
        /// </summary>
        public void Clear()
        {
            this.ExecuteWithLock(this.ClearInternal);
        }

        private void ClearInternal()
        {
            this.list.Clear();
        }

        /// <summary>
        /// Determines whether a value is in the list.
        /// </summary>
        /// <param name="item">The value to locate in the list.</param>
        /// <returns><c>true</c> if the item can be found in the list, otherwise <c>false</c>.</returns>
        public bool Contains(T item)
        {
            return this.ExecuteWithLock<T, bool>(this.ContainsInternal, item);
        }

        private bool ContainsInternal(T item)
        {
            return this.list.Contains(item);
        }

        /// <summary>
        /// Removes the first occurrence of the specified value from the list.
        /// </summary>
        /// <param name="item">The value to remove from the list.</param>
        /// <returns><c>true</c> if the element containing value is successfully removed; otherwise, <c>false</c>. 
        /// This method also returns <c>false</c> if value was not found in the original list.</returns>
        public bool Remove(T item)
        {
            return this.ExecuteWithLock<T, bool>(this.RemoveInternal, item);
        }

        private bool RemoveInternal(T item)
        {
            return this.list.Remove(item);
        }

        /// <summary>
        /// Removes the first item from the list.
        /// </summary>
        /// <exception cref="InvalidOperationException">If the list is empty.</exception>
        public void RemoveFirst()
        {
            this.ExecuteWithLock(this.RemoveFirstInternal);
        }

        private void RemoveFirstInternal()
        {
            this.list.RemoveFirst();
        }

        /// <summary>
        /// Removes the last item from the list.
        /// </summary>
        /// <exception cref="InvalidOperationException">If the list is empty.</exception>
        public void RemoveLast()
        {
            this.ExecuteWithLock(this.RemoveLastInternal);
        }

        private void RemoveLastInternal()
        {
            this.list.RemoveLast();
        }
        #endregion

        #region IEnumerable<T>
        /// <summary>
        /// Returns an enumerator that iterates through the collection.
        /// </summary>
        /// <returns>
        /// A <see cref="T:System.Collections.Generic.IEnumerator`1" /> that can be used to iterate through the collection.
        /// </returns>
        public IEnumerator<T> GetEnumerator()
        {
            return this.ToList().GetEnumerator();
        }
        #endregion

        #region IEnumerable
        /// <summary>
        /// Returns an enumerator that iterates through a collection.
        /// </summary>
        /// <returns>
        /// An <see cref="T:System.Collections.IEnumerator" /> object that can be used to iterate through the collection.
        /// </returns>
        IEnumerator IEnumerable.GetEnumerator()
        {
            return (IEnumerator)this.GetEnumerator();
        }
        #endregion

        #region ICollection
        /// <summary>
        /// Copies the elements of the <see cref="T:System.Collections.ICollection" /> to an <see cref="T:System.Array" />, starting at a particular <see cref="T:System.Array" /> index.
        /// </summary>
        /// <param name="array">The one-dimensional <see cref="T:System.Array" /> that is the destination of the elements copied from <see cref="T:System.Collections.ICollection" />. The <see cref="T:System.Array" /> must have zero-based indexing.</param>
        /// <param name="index">The zero-based index in <paramref name="array" /> at which copying begins.</param>
        public void CopyTo(Array array, int index)
        {
            this.ToArray().CopyTo(array, index);
        }

        /// <summary>
        /// Gets the number of elements contained in the <see cref="T:System.Collections.ICollection" />.
        /// </summary>
        /// <returns>The number of elements contained in the <see cref="T:System.Collections.ICollection" />.</returns>
        public int Count
        {
            get
            {
                return this.ExecuteWithLock<int>(this.CountInternal);
            }
        }

        private int CountInternal()
        {
            return this.list.Count;
        }

        /// <summary>
        /// Gets an object that can be used to synchronize access to the <see cref="T:System.Collections.ICollection" />.
        /// </summary>
        /// <returns>An object that can be used to synchronize access to the <see cref="T:System.Collections.ICollection" />.</returns>
        /// <exception cref="System.NotSupportedException">Concurrent collections do not support SyncRoot.</exception>
        public object SyncRoot
        {
            get
            {
                throw new NotSupportedException("Concurrent collections do not support SyncRoot.");
            }
        }

        /// <summary>
        /// Gets a value indicating whether access to the <see cref="T:System.Collections.ICollection" /> is synchronized (thread safe).
        /// </summary>
        /// <returns>true if access to the <see cref="T:System.Collections.ICollection" /> is synchronized (thread safe); otherwise, false.</returns>
        public bool IsSynchronized
        {
            get
            {
                return false;
            }
        }
        #endregion

        #region IProducerConsumerCollection<T>
        /// <summary>
        /// Copies the entire list to a compatible one-dimensional Array, starting at the specified index of the target array.
        /// </summary>
        /// <param name="array">The one-dimensional Array that is the destination of the elements copied from list. The Array must have zero-based indexing.</param>
        /// <param name="index">The zero-based index in array at which copying begins.</param>
        public void CopyTo(T[] array, int index)
        {
            this.ExecuteWithLock(this.CopyToInternal, new Tuple<T[], int>(array, index));
        }

        private void CopyToInternal(Tuple<T[], int> args)
        {
            this.list.CopyTo(args.Item1, args.Item2);
        }

        /// <summary>
        /// Attempts to add an object to the <see cref="T:System.Collections.Concurrent.IProducerConsumerCollection`1" />.
        /// </summary>
        /// <param name="item">The object to add to the <see cref="T:System.Collections.Concurrent.IProducerConsumerCollection`1" />.</param>
        /// <returns>
        /// true if the object was added successfully; otherwise, false.
        /// </returns>
        public bool TryAdd(T item)
        {
            this.AddLast(item);
            return true;
        }

        /// <summary>
        /// Attempts to remove and return an object from the <see cref="T:System.Collections.Concurrent.IProducerConsumerCollection`1" />.
        /// </summary>
        /// <param name="item">When this method returns, if the object was removed and returned successfully, <paramref name="item" /> contains the removed object. If no object was available to be removed, the value is unspecified.</param>
        /// <returns>
        /// true if an object was removed and returned successfully; otherwise, false.
        /// </returns>
        public bool TryTake(out T item)
        {
            var result = this.ExecuteWithLock<Result<T>>(this.TryTakeInternal);
            item = result.Value;
            return result.IsSuccess;
        }

        private Result<T> TryTakeInternal()
        {
            if (this.list.First == null)
            {
                return new Result<T>(false);
            }

            var item = this.list.First.Value;
            this.list.RemoveFirst();
            return new Result<T>(true, item);
        }

        /// <summary>
        /// Copies the elements contained in the <see cref="T:System.Collections.Concurrent.IProducerConsumerCollection`1" /> to a new array.
        /// </summary>
        /// <returns>
        /// A new array containing the elements copied from the <see cref="T:System.Collections.Concurrent.IProducerConsumerCollection`1" />.
        /// </returns>
        public T[] ToArray()
        {
            return this.ToList().ToArray();
        }
        #endregion

        private List<T> ToList()
        {
            return this.ExecuteWithLock<List<T>>(this.ToListInternal);
        }

        private List<T> ToListInternal()
        {
            var result = new List<T>(this.list.Count);
            foreach (var item in this.list)
            {
                result.Add(item);
            }

            return result;
        }

        private void LockCollection()
        {
            bool lockTaken = false;
            this.listLock.Enter(ref lockTaken);
            if (!lockTaken)
            {
                throw new InvalidOperationException("Failed to aquire the lock");
            }
        }

        private void ReleaseCollection()
        {
            this.listLock.Exit(false);
        }

        private void ExecuteWithLock(Action action)
        {
            this.LockCollection();
            try
            {
                action();
            }
            finally
            {
                this.ReleaseCollection();
            }
        }

        private void ExecuteWithLock<Tparam>(Action<Tparam> action, Tparam arg)
        {
            this.LockCollection();
            try
            {
                action(arg);
            }
            finally
            {
                this.ReleaseCollection();
            }
        }

        private Tresult ExecuteWithLock<Tresult>(Func<Tresult> action)
        {
            this.LockCollection();
            try
            {
                return action();
            }
            finally
            {
                this.ReleaseCollection();
            }
        }

        private Tresult ExecuteWithLock<Tparam, Tresult>(Func<Tparam, Tresult> action, Tparam arg)
        {
            this.LockCollection();
            try
            {
                return action(arg);
            }
            finally
            {
                this.ReleaseCollection();
            }
        }
    }
}
